﻿using System;
using System.Windows.Forms;
using System.Xml;
using System.IO;
using System.Xml.Serialization;

namespace QandA
{
    public partial class MainForm : Form
    {
        private QuestionBankQuestion CurrentQuestion;
        private QuestionBankRoot QuestionBank;
        private Random RandomGenerator;
        private QuestionBankCategories ValidCategories;
        private QuestionBankTestDefinition pTestInProgress;

        private int pQuestionsAttempted;
        private int pQuestionsCorrect;
        private string pTestMode;
        private Boolean pSupprestNextNextClick;

        private int pCurrentTestCategory;
        private int pCurrentCategoryDone;

        private int pTestCategoriesCorrect;

        private bool pOnlyUseQuestionsOnce;
        private bool pOnlyUseCategoriesOnce;

        #region Constructor
        /// <summary>
        /// Initializes the main window and attempts to load a set of questions.
        /// If none can be loaded, the program exits with an error. The program
        /// starts in "Free question mode"
        /// </summary>
        public MainForm()
        {
            InitializeComponent();
            RandomGenerator = new Random();
            if (!LoadQuestionBank(MainSettings.Default.LastQuestionFile))
            {
                MessageBox.Show("Could not open default data file - please select another", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                if (openFileDialog.ShowDialog() != DialogResult.OK ||
                   !LoadQuestionBank(openFileDialog.FileName))
                {
                    MessageBox.Show("Failed to start properly, aborting", "Abort", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                    System.Environment.Exit(1);
                }
            }
            QuestionsCorrectCount = 0;
            QuestionsAttemptedCount = 0;
            TestMode = "Free Question Mode";
            pSupprestNextNextClick = false;

            OnQuestionSelectionClick(fullyRandomToolStripMenuItem, null);

            BeginTest(null);
        }
        #endregion

        #region Event Handlers

        /// <summary>
        /// Causes a set of questions to be loaded, cancels any tests in progress
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnFileLoadClick(object sender, EventArgs e)
        {
            //Do some stuff.
            if (!ShowLoadQuestionBank())
            {
                return;
            }
            BeginTest(null);
        }

        /// <summary>
        /// Determines which answer was selected and submits that to the question
        /// management system
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnAnswerClick(object sender, EventArgs e)
        {
            String letter;
            letter = "";
            if (sender == cmdAnswerA)
            {
                letter = "a";
            }
            else if (sender == cmdAnswerB)
            {
                letter = "b";
            }
            else if (sender == cmdAnswerC)
            {
                letter = "c";
            }
            else if (sender == cmdAnswerD)
            {
                letter = "d";
            }

            AnswerQuestion(letter);
        }

        /// <summary>
        /// Finishes this question and moves on to the next one
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnNextClick(object sender, EventArgs e)
        {
            if (!pSupprestNextNextClick)
            {
                QuestionsAttemptedCount++;
                DoTestEndedCheck();
            }
            else
            {
                pSupprestNextNextClick = false;
            }
            NextQuestion();
        }

        /// <summary>
        /// Called when the user wants to limit themselves to only certain questions.
        /// Displays a form to allow them to do this.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnCategoriesClick(object sender, EventArgs e)
        {
            bool ValidResponse = false;
            CategoriesForm frm = new CategoriesForm();

            while (!ValidResponse)
            {
                frm.Categories = QuestionBank.Categories;
                frm.CheckedCategories = ValidCategories;
                if (frm.ShowDialog() != DialogResult.OK)
                {
                    //Make no changes
                    return;
                }

                if (frm.CheckedCategories.Count == 0)
                {
                    MessageBox.Show("You must select at least some categories, or click Cancel", "Categories", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
                else
                {
                    //Make the changes.
                    ValidCategories = frm.CheckedCategories;
                    NextQuestion();
                    return;
                }
            }
        }

        /// <summary>
        /// Resets the user's score in Free question mode.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnResetQuestionCount(object sender, EventArgs e)
        {
            ResetCounters();
        }

        /// <summary>
        /// Allows the user to select a test and then begins it immediately
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnBeginTestClick(object sender, EventArgs e)
        {
            TestsForm frm = new TestsForm();
            try
            {
                frm.TestsList = QuestionBank.Tests;
                if (frm.ShowDialog() != DialogResult.OK)
                {
                    return;
                }
                if (frm.SelectedTest == null)
                {
                    MessageBox.Show("No test selected, returning to free-question mode", "Question Mode", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    TestInProgress = null;
                    NextQuestion();
                }
                BeginTest(frm.SelectedTest);
            }
            catch (Exception)
            {
                MessageBox.Show("Error selecting test, reverting to free-question mode");
                AbortTest();
            }
        }

        /// <summary>
        /// Called when a question selection mode menu optino is clicked,
        /// changing how questions are picked.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnQuestionSelectionClick(object sender, EventArgs e)
        {
            if (sender == fullyRandomToolStripMenuItem)
            {
                pOnlyUseQuestionsOnce = false;
                pOnlyUseCategoriesOnce = false;

                fullyRandomToolStripMenuItem.Checked = true;
                onceUntilAllSeenToolStripMenuItem.Checked = false;
                onceUntilWholeCategorySeenToolStripMenuItem.Checked = false;

                ResetRemainingQuestions();
            }
            else if (sender == onceUntilAllSeenToolStripMenuItem)
            {
                pOnlyUseQuestionsOnce = true;
                pOnlyUseCategoriesOnce = true;
                fullyRandomToolStripMenuItem.Checked = true;
                onceUntilAllSeenToolStripMenuItem.Checked = false;
                onceUntilWholeCategorySeenToolStripMenuItem.Checked = false;

                ResetRemainingQuestions();
            }
            else if (sender == onceUntilWholeCategorySeenToolStripMenuItem)
            {
                pOnlyUseQuestionsOnce = true;
                pOnlyUseCategoriesOnce = false;
                fullyRandomToolStripMenuItem.Checked = false;
                onceUntilAllSeenToolStripMenuItem.Checked = false;
                onceUntilWholeCategorySeenToolStripMenuItem.Checked = true;

                ResetRemainingQuestions();
            }
            else
            {
                //Don't know what to do here!
            }
        }

        #endregion

        #region File Loading

        /// <summary>
        /// Displays a dialog box allowing the user to select a set of questions.
        /// Any test in progress is cancelled.
        /// </summary>
        /// <returns>true if a new set of questions was loaded, false otherwise</returns>
        private Boolean ShowLoadQuestionBank()
        {
            if (openFileDialog.ShowDialog() != DialogResult.OK)
            {
                return false;
            }
            if (!LoadQuestionBank(openFileDialog.FileName))
            {
                MessageBox.Show("Could not open the data file.", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            BeginTest(null);

            return true;
        }

        /// <summary>
        /// Attempts to load a specified bank of questions from the disk
        /// </summary>
        /// <param name="fn">The filename of the XML file containing the questions</param>
        /// <returns>true upon success, false otherwise</returns>
        private Boolean LoadQuestionBank(String fn)
        {
            if (!File.Exists(fn))
            {
                return false;
            }

            try
            {
                XmlSerializer s = new XmlSerializer(typeof(QuestionBankRoot));
                QuestionBankRoot newList;
                XmlTextReader r = new XmlTextReader(fn);
                newList = (QuestionBankRoot)s.Deserialize(r);
                r.Close();

                QuestionBank = newList;

                //MaxQuestionIndex = QuestionBank.Questions.GetCount();
            }
            catch(Exception)
            {
                return false;
            }

            ValidCategories = QuestionBank.Categories;
            MainSettings.Default.LastQuestionFile = fn;
            MainSettings.Default.Save();
            return true;
        }

        #endregion

        #region Question Handling

        /// <summary>
        /// Randomly selects the next question from the available categories
        /// </summary>
        /// <returns>The next question to be answered</returns>
        private QuestionBankQuestion SelectNextQuestion()
        {
            QuestionBankQuestion x;

            if (TestInProgress == null)
            {
                int CatIdx = RandomGenerator.Next(0, ValidCategories.Count);

                if (pOnlyUseQuestionsOnce)
                {
                    x = ValidCategories[CatIdx].GetQuestionOnce();
                }
                else
                {
                    int Idx = RandomGenerator.Next(0, ValidCategories[CatIdx].QuestionCount);
                    x = ValidCategories[CatIdx].Questions[Idx];
                }
            }
            else
            {
                try
                {
                    QuestionBankCategory l = QuestionBank.Categories.GetByCategoryID(TestInProgress.Sections[pCurrentTestCategory].SectionCategoryNum);

                    if (pOnlyUseQuestionsOnce)
                    {
                        x = l.GetQuestionOnce();
                    }
                    else
                    {
                        int Idx = RandomGenerator.Next(0, l.QuestionCount);
                        x = l.Questions[Idx];
                    }
                }
                catch (Exception)
                {
                    return null;
                }
            }

            return x;
        }

        /// <summary>
        /// Selects the next question, displays it on screen and allows the user
        /// to select an answer
        /// </summary>
        /// <returns>true upon success, exits upon failure</returns>
        private Boolean NextQuestion()
        {
            try
            {
                QuestionBankQuestion x = SelectNextQuestion();
                if (x==null)
                {
                    throw new InvalidDataException();
                }

                lblAnswerA.Text = x.Answers[0].Value;
                lblAnswerB.Text = x.Answers[1].Value;
                lblAnswerC.Text = x.Answers[2].Value;
                lblAnswerD.Text = x.Answers[3].Value;
                lblQuestion.Text = x.Text;
                lblResult.Text = "So, what do you think?";

                CurrentQuestion = x;

                cmdAnswerA.Enabled = true;
                cmdAnswerB.Enabled = true;
                cmdAnswerC.Enabled = true;
                cmdAnswerD.Enabled = true;

                return true;
            }
            catch (Exception)
            {
                MessageBox.Show("Failed to grab the next question, the data file may be corrupt - please select another", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                if (!ShowLoadQuestionBank())
                {
                    MessageBox.Show("Failed to load a new data set, terminating", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    System.Environment.Exit(1);
                }
            }

            return NextQuestion();
        }

        /// <summary>
        /// Called when the user answers a question. Decides whether the answer is correct,
        /// displays the appropriate message and updates the scores. Also calls into the
        /// test functions to decide if it is time to end a test or switch sections
        /// </summary>
        /// <param name="Letter">The answer given</param>
        /// <returns>true, always</returns>
        private Boolean AnswerQuestion(String Letter)
        {
            string output;
            if (Letter == CurrentQuestion.CorrectAnswer)
            {
                output = "CORRECT! ";
                QuestionsCorrectCount++;
            }
            else
            {
                output = "WRONG! The correct answer is " + CurrentQuestion.CorrectAnswer + ". ";
            }

            output += CurrentQuestion.Context;
            lblResult.Text = output;

            cmdAnswerA.Enabled = false;
            cmdAnswerB.Enabled = false;
            cmdAnswerC.Enabled = false;
            cmdAnswerD.Enabled = false;

            pSupprestNextNextClick = true;
            
            QuestionsAttemptedCount++;
            DoTestEndedCheck();

            return true;
        }

        #endregion

        #region Test Handling

        /// <summary>
        /// Called at the end of a test section, displays a quick message to the user
        /// </summary>
        private void EndOfSectionReport()
        {
            string MessageBoxString;
            if (QuestionsCorrectCount >= TestInProgress.Sections[pCurrentTestCategory].SectionPassScore)
            {
                MessageBoxString = "Section Passed. ";
                pTestCategoriesCorrect++;
            }
            else
            {
                MessageBoxString = "Section Failed. ";
            }
            MessageBoxString += "Got " + QuestionsCorrectCount.ToString() + " correct, "
               + TestInProgress.Sections[pCurrentTestCategory].SectionPassScore.ToString() + " needed.";
            MessageBox.Show(MessageBoxString, "Section Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
            ResetCounters();
        }

        /// <summary>
        /// At the end of a section, switches sections. At the end of the test,
        /// causes the program to revert to free-question mode.
        /// </summary>
        private void DoTestEndedCheck()
        {
            if (pTestInProgress != null)
            {
                pCurrentCategoryDone++;
                if (pCurrentCategoryDone == TestInProgress.Sections[pCurrentTestCategory].QuestionsInSection)
                {
                    EndOfSectionReport();
                    pCurrentTestCategory++;
                    pCurrentCategoryDone = 0;
                    if (pCurrentTestCategory == TestInProgress.Sections.GetLength(0))
                    {
                        FinishTest();
                    }
                }
            }
        }

        /// <summary>
        /// The testing mode to be displayed to the user in the status bar.
        /// </summary>
        protected String TestMode
        {
            get
            {
                return pTestMode;
            }
            set
            {
                pTestMode = value;
                StatusTestMode.Text = value;
            }
        }

        /// <summary>
        /// The test the user is currently taking, null in free question mode
        /// </summary>
        protected QuestionBankTestDefinition TestInProgress
        {
            get
            {
                return pTestInProgress;
            }
            set
            {
                pTestInProgress = value;
                ResetCounters();
                if (value != null)
                {
                    TestMode = "Testing: " + value.Name;
                }
                else
                {
                    TestMode = "Free Question Mode";
                }
            }
        }

        /// <summary>
        /// Sets up the program to begin a test
        /// </summary>
        /// <param name="def">The test definition for the test the user is taking</param>
        public void BeginTest(QuestionBankTestDefinition def)
        {
            ResetRemainingQuestions();
            pCurrentTestCategory = 0;
            pCurrentCategoryDone = 0;
            pTestCategoriesCorrect = 0;
            pSupprestNextNextClick = false;
            TestInProgress = def;
            NextQuestion();
        }

        /// <summary>
        /// Cancels a test, for example when a new bank of questions is loaded
        /// </summary>
        public void AbortTest()
        {
            ResetRemainingQuestions();
            TestInProgress = null;
            ResetCounters();
            NextQuestion();
        }

        /// <summary>
        /// Called at the end of the test to display a message to the user about their
        /// score
        /// </summary>
        public void FinishTest()
        {
            //Do some other stuff, then AbortTest();
            int TestCategoriesCount = TestInProgress.Sections.GetLength(0);
            string z = "Test Complete. Passed " + pTestCategoriesCorrect.ToString() +
                       " of " + TestCategoriesCount.ToString() +
                       " sections";
            MessageBox.Show(z, "Test Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
            AbortTest();
        }

        /// <summary>
        /// Reset all categories to have all their original questions
        /// </summary>
        public void ResetRemainingQuestions()
        {
            QuestionBank.ResetRemainingQuestions();
        }

        #endregion

        #region Scoring

        private void ResetCounters()
        {
            QuestionsAttemptedCount = 0;
            QuestionsCorrectCount = 0;
        }

        /// <summary>
        /// Displays the percentage of correctly answered questions in the status bar.
        /// </summary>
        private void SetPercentCorrectStatus()
        {
            String pct;
            if (QuestionsAttemptedCount != 0)
            {
                float pc = (float)QuestionsCorrectCount / (float)QuestionsAttemptedCount;
                pc = pc * 100;
                pct = String.Format("{0: 0}", pc);
            }
            else
            {
                pct = "--";
            }

            StatusPercentCorrect.Text = pct + "% Correct";
        }

        protected int QuestionsAttemptedCount
        {
            get
            {
                return pQuestionsAttempted;
            }
            set
            {
                pQuestionsAttempted = value;
                StatusQuestionsAttempted.Text = "Attempted: " + pQuestionsAttempted.ToString();
                SetPercentCorrectStatus();
            }
        }

        protected int QuestionsCorrectCount
        {
            get
            {
                return pQuestionsCorrect;
            }
            set
            {
                pQuestionsCorrect = value;
                StatusQuestionsCorrect.Text = "Correct: " + pQuestionsCorrect.ToString();
                SetPercentCorrectStatus();
            }
        }

        #endregion
    }
}
